package client;

import com.thoughtworks.xstream.XStream;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.http.*;
import org.jboss.netty.util.CharsetUtil;
import server.ServerConstants;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

/**
 * Created by IntelliJ IDEA.
 * User: Hp
 * Date: 23.04.11
 * Time: 21:58
 * To change this template use File | Settings | File Templates.
 */
public class ClientTCP extends SimpleChannelHandler implements ClientInterface, ChannelPipelineFactory, Runnable {
    static Logger logger = Logger.getLogger(ClientTCP.class.getCanonicalName());
    private ClientListener clListener;
    private ClientBootstrap bootStrap;
    private Channel channel;
    private boolean connected;
    private XStream xstream;
    private int lastAction = 0;
    private boolean active;

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
        logger.log(Level.WARNING, "client channel exception", e.getCause());
        e.getChannel().close();
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        final HttpResponse response = (HttpResponse) e.getMessage();
        if (response.getStatus().getCode() != HttpResponseStatus.OK.getCode())
            return;
        if (response.getHeader(HttpHeaders.Names.CONTENT_TYPE).equals("text/xml")) {
            Object obj = xstream.fromXML(response.getContent().toString(CharsetUtil.UTF_8));
            final ArrayList<PaintAction> actions = (ArrayList<PaintAction>) obj;
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    clListener.beginUpdate();
                    for (PaintAction action : actions) {
                        clListener.onAction(action);
                        lastAction = Math.max(action.getID(), lastAction);
                    }
                    clListener.endUpdate();
                }
            });
        } else if (response.getHeader(HttpHeaders.Names.CONTENT_TYPE).equals("image/png")) {
            int len = response.getContent().readableBytes();
            byte[] bytes = new byte[len];
            response.getContent().readBytes(bytes);
            ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
            final BufferedImage img = ImageIO.read(stream);
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    clListener.onImage(img);
                    lastAction = Integer.parseInt(response.getHeader(ServerConstants.VERSION_HEADER));
                }
            });
        }
//
    }

    @Override
    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        super.channelClosed(ctx, e);    //To change body of overridden methods use File | Settings | File Templates.
        connected = false;
    }


    public ClientTCP() {
        xstream = new XStream();
        xstream.alias("paintaction", PaintAction.class);
    }

    public boolean isConnected() {
        return connected;
    }

    public void setClientListener(ClientListener cl) {
        clListener = cl;
        //To change body of implemented methods use File | Settings | File Templates.
    }

    public void sendAction(PaintAction action) {
        if (!connected) return;
        String uri = "/newaction";
        HttpRequest request = generateRequest(uri);
        request.setContent(ChannelBuffers.copiedBuffer(xstream.toXML(action), CharsetUtil.UTF_8));
        request.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/xml");
        request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, request.getContent().readableBytes());
        channel.write(request);
    }

    public HttpRequest generateRequest(String uri) {
        HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri);
        request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        request.setHeader(HttpHeaders.Names.HOST, ServerConstants.serverHost);
        return request;
    }

    public void sendImageRequest() {
        if (!connected) return;
        HttpRequest request = generateRequest("/image");
        channel.write(request);

    }

    public void connect() {
        bootStrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool(),
                Executors.newCachedThreadPool()));
        bootStrap.setPipelineFactory(this);
        new Thread(this).start();
    }

    public ChannelPipeline getPipeline() throws Exception {
        ChannelPipeline pipe = Channels.pipeline(new HttpClientCodec(), new HttpChunkAggregator(1 << 24), this);
        return pipe;  //To change body of implemented methods use File | Settings | File Templates.
    }

    public void run() {
        while (true) {
            if (active) {
                if (connected) {
                    HttpRequest request = generateRequest("updates?number=" + lastAction);
                    channel.write(request);
                } else {
                    lastAction = 0;
                    ChannelFuture future = bootStrap.connect(new InetSocketAddress(ServerConstants.serverHost, ServerConstants.port));
                    channel = future.awaitUninterruptibly().getChannel();
                    if (!future.isSuccess()) {
                        logger.log(Level.WARNING, "Client disconnected");
                        System.out.println(ServerConstants.port + " ");
                    } else {
                        connected = true;
                        logger.log(Level.INFO,"Client connected");
                    }
                }
            } else if (connected) {
                channel.disconnect();
                connected = false;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                //ignored
            }
        }
    }
}
